package com.xyazm.order.service.impl;


import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.xyazm.common.constants.RedisLockKeyConstants;
import com.xyazm.common.enums.OrderStatusChangeEnum;
import com.xyazm.common.enums.OrderStatusEnum;
import com.xyazm.common.redis.RedisLock;
import com.xyazm.common.utils.ParamCheckUtil;
import com.xyazm.order.constants.OrderConstants;
import com.xyazm.order.dao.OrderDeliveryDetailDAO;
import com.xyazm.order.dao.OrderInfoDAO;
import com.xyazm.order.dao.OrderPaymentDetailDAO;
import com.xyazm.order.domain.dto.CreateOrderDTO;
import com.xyazm.order.domain.dto.PrePayOrderDTO;
import com.xyazm.order.domain.entity.OrderDeliveryDetailDO;
import com.xyazm.order.domain.entity.OrderInfoDO;
import com.xyazm.order.domain.entity.OrderPaymentDetailDO;
import com.xyazm.order.domain.param.*;
import com.xyazm.order.enums.DeleteStatusEnum;
import com.xyazm.order.enums.OrderNoTypeEnum;
import com.xyazm.order.enums.PayStatusEnum;
import com.xyazm.order.enums.PayTypeEnum;
import com.xyazm.order.exception.OrderBizException;
import com.xyazm.order.exception.OrderErrorCodeEnum;
import com.xyazm.order.manager.IOrderNoManager;
import com.xyazm.order.mapstruct.OrderApiMapstruct;
import com.xyazm.order.remote.PayRemote;
import com.xyazm.order.service.IOrderService;
import com.xyazm.order.statemachine.OrderStateMachine;
import com.xyazm.order.statemachine.StateMachineFactory;
import com.xyazm.pay.domain.dto.PayOrderDTO;
import com.xyazm.pay.domain.param.PayOrderParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


@Slf4j
@Service
public class OrderServiceImpl implements IOrderService {

    @Autowired
    private OrderInfoDAO orderInfoDAO;
    @Autowired
    private OrderDeliveryDetailDAO orderDeliveryDetailDAO;
    @Autowired
    private OrderPaymentDetailDAO orderPaymentDetailDAO;
    /**
     * 订单号生成器
    */
    @Autowired
    private IOrderNoManager orderNoManager;
    /**
     * 支付服务
     */
    @Autowired
    private PayRemote payRemote;


    @Autowired
    private RedisLock redisLock;

    @Autowired
    private StateMachineFactory stateMachineFactory;


    @Override
    public String genOrderId(GenOrderIdParam param) {
        return orderNoManager.genOrderId(OrderNoTypeEnum.ORDER_SALE.getCode(), param.getUserId());
    }

    @Override
    public CreateOrderDTO createOrder(CreateOrderParam param) {
        OrderStateMachine orderStateMachine = stateMachineFactory.getOrderStateMachine(OrderStatusEnum.NULL);
        orderStateMachine.fire(OrderStatusChangeEnum.ORDER_CREATED, param);
        return new CreateOrderDTO(param.getOrderId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PrePayOrderDTO prePayOrder(PrePayOrderParam param) {
        // 校验预支付订单请求参数
        checkPrePayOrderParam(param);
        String orderId = param.getOrderId();
        Integer payAmount = param.getPayAmount();

        // 加分布式锁（与订单支付回调时加的是同一把锁）
        String key = RedisLockKeyConstants.ORDER_PAY_KEY + orderId;
        boolean lock = redisLock.tryLock(key);
        if (!lock) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_PRE_PAY_ERROR);
        }
        try {
            // 幂等性检查
            checkPrePayOrderInfo(orderId,payAmount);
            PayOrderParam payOrderParam = OrderApiMapstruct.INSTANCE.convertPayOrderParam(param);
            // 调用支付系统进行预支付下单
            PayOrderDTO payOrderDTO = payRemote.payOrder(payOrderParam);
            // 状态机流转 -> 更新支付信息
            OrderStatusChangeEnum statusChangeEnum = OrderStatusChangeEnum.ORDER_PREPAY;
            OrderStateMachine orderStateMachine = stateMachineFactory.getOrderStateMachine(statusChangeEnum.getFromStatus());
            orderStateMachine.fire(statusChangeEnum,payOrderDTO);
            return OrderApiMapstruct.INSTANCE.convertPrePayOrderDTO(payOrderDTO);
        } finally {
            // 释放分布式锁
            redisLock.unlock(key);
        }
    }

    /**
     * 支付回调
     * 支付回调有2把分布式锁的原因说明：同一笔订单在同一时间只能支付or取消
     * 不可以同时对一笔订单，既发起支付，又发起取消
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void payCallback(PayCallbackParam param) {
        // 参数校验
        checkPayCallbackParam(param);
        // 状态机流转 -> 订单已支付
        OrderStatusChangeEnum event = OrderStatusChangeEnum.ORDER_PAID;
        OrderStateMachine orderStateMachine = stateMachineFactory.getOrderStateMachine(event.getFromStatus());
        orderStateMachine.fire(event, param);
    }




    @Override
    public void removeOrders(RemoveOrderParam param) {
        // 1、参数校验
        Set<String> orderIdSet = param.getOrderIds();
        ParamCheckUtil.checkCollectionNonEmpty(orderIdSet, OrderErrorCodeEnum.ORDER_ID_IS_NULL);
        ParamCheckUtil.checkSetMaxSize(orderIdSet, OrderConstants.REMOVE_ORDER_MAX_COUNT,
                OrderErrorCodeEnum.COLLECTION_PARAM_CANNOT_BEYOND_MAX_SIZE, "orderIds"
                , OrderConstants.REMOVE_ORDER_MAX_COUNT);

        // 2、根据id查询订单
        List<String> orderIds = new ArrayList<>(orderIdSet);
        List<OrderInfoDO> orderInfoDOS = orderInfoDAO.listByOrderIds(orderIds);
        if (CollectionUtils.isEmpty(orderInfoDOS)) {
            return;
        }

        // 3、校验订单是否可以移除
        orderInfoDOS.forEach(orderInfo -> {
            if (!canRemove(orderInfo)) {
                throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANNOT_REMOVE);
            }
        });

        // 4、对订单进行软删除
        List<Long> ids = orderInfoDOS.stream().map(OrderInfoDO::getId).collect(Collectors.toList());
        orderInfoDAO.softRemoveOrders(ids);

    }


    @Override
    public void adjustDeliveryAddress(AdjustDeliveryAddressParam param) {
        // 1、参数校验
        ParamCheckUtil.checkStringNonEmpty(param.getOrderId(), OrderErrorCodeEnum.ORDER_ID_IS_NULL);

        // 2、根据订单id查询订单
        OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(param.getOrderId());
        ParamCheckUtil.checkObjectNonNull(orderInfoDO, OrderErrorCodeEnum.ORDER_NOT_FOUND);

        // 3、校验订单是否未出库
        if (!OrderStatusEnum.unOutStockStatus().contains(orderInfoDO.getOrderStatus())) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_NOT_ALLOW_TO_ADJUST_ADDRESS);
        }

        // 4、查询订单配送信息
        OrderDeliveryDetailDO orderDeliveryDetailDO = orderDeliveryDetailDAO.getByOrderId(param.getOrderId());
        if (orderDeliveryDetailDO == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_DELIVERY_NOT_FOUND);
        }

        // 5、校验配送信息是否已经被修改过一次
        if (orderDeliveryDetailDO.getModifyAddressCount() > 0) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_DELIVERY_ADDRESS_HAS_BEEN_ADJUSTED);
        }

        // 6、更新配送地址信息
        orderDeliveryDetailDAO.updateDeliveryAddress(orderDeliveryDetailDO.getId() ,
                orderDeliveryDetailDO.getModifyAddressCount(), param);
    }


    /**
     * 校验预支付订单请求参数
     * @author xuyou
     * 2022/6/16 0016 21:04
     * @param param
    */
    private void checkPrePayOrderParam(PrePayOrderParam param) {

        String userId = param.getUserId();
        ParamCheckUtil.checkStringNonEmpty(userId, OrderErrorCodeEnum.USER_ID_IS_NULL);

        String businessIdentifier = param.getBusinessIdentifier();
        ParamCheckUtil.checkStringNonEmpty(businessIdentifier, OrderErrorCodeEnum.BUSINESS_IDENTIFIER_ERROR);

        Integer payType = param.getPayType();
        ParamCheckUtil.checkObjectNonNull(payType, OrderErrorCodeEnum.PAY_TYPE_PARAM_ERROR);
        if (PayTypeEnum.getByCode(payType) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.PAY_TYPE_PARAM_ERROR);
        }

        String orderId = param.getOrderId();
        ParamCheckUtil.checkStringNonEmpty(orderId, OrderErrorCodeEnum.ORDER_ID_IS_NULL);

        Integer payAmount = param.getPayAmount();
        ParamCheckUtil.checkObjectNonNull(payAmount, OrderErrorCodeEnum.ORDER_REAL_PAY_AMOUNT_IS_NULL);

    }

    /**
     * 预支付订单的前置检查
     * @author xuyou
     * 2022/6/16 0016 21:30
     * @param orderId 订单id
     * @param payAmount 订单支付金额
    */
    private void checkPrePayOrderInfo(String orderId, Integer payAmount) {
        // 查询订单信息
        OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(orderId);
        if (orderInfoDO == null ) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_INFO_IS_NULL);
        }
        // 查询订单支付明细信息
        OrderPaymentDetailDO orderPaymentDetailDO = orderPaymentDetailDAO.getPaymentDetailByOrderId(orderId);
        if (orderPaymentDetailDO == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_INFO_IS_NULL);
        }

        // 检查订单支付金额
        if (!payAmount.equals(orderInfoDO.getPayAmount())) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_PAY_AMOUNT_ERROR);
        }

        // 判断一下订单状态
        if (!OrderStatusEnum.CREATED.getCode().equals(orderInfoDO.getOrderStatus())) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_STATUS_ERROR);
        }

        // 判断一下支付状态
        if (PayStatusEnum.PAID.getCode().equals(orderPaymentDetailDO.getPayStatus())) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_PAY_STATUS_IS_PAID);
        }

        // 判断是否超过了支付超时时间
        if(new Date().after(orderInfoDO.getExpireTime())) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_PRE_PAY_EXPIRE_ERROR);
        }

    }


    /**
     * 校验支付回调入参
     * @author xuyou
     * @date: 2022/6/23 15:32
     */
    private void checkPayCallbackParam(PayCallbackParam param) {
        ParamCheckUtil.checkObjectNonNull(param);
        // 订单号
        String orderId = param.getOrderId();
        ParamCheckUtil.checkStringNonEmpty(orderId);
        // 支付金额
        Integer payAmount = param.getPayAmount();
        ParamCheckUtil.checkObjectNonNull(payAmount);
        // 支付系统交易流水号
        String outTradeNo = param.getOutTradeNo();
        ParamCheckUtil.checkStringNonEmpty(outTradeNo);
        // 商户ID
        String merchantId = param.getMerchantId();
        ParamCheckUtil.checkStringNonEmpty(merchantId);
        // 支付类型
        Integer payType = param.getPayType();
        ParamCheckUtil.checkObjectNonNull(payType);
        if (PayTypeEnum.getByCode(payType) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.PAY_TYPE_PARAM_ERROR);
        }
    }


    /**
     * 校验订单能否删除
     * @author xuyou
     * 2022/6/21 0021 22:59
     * @param orderInfoDO
     * @return boolean
     */
    private boolean canRemove(OrderInfoDO orderInfoDO) {
        return OrderStatusEnum.canRemoveStatus().contains(orderInfoDO.getOrderStatus()) &&
                DeleteStatusEnum.NO.getCode().equals(orderInfoDO.getDeleteStatus());
    }


}
